﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Metadata;
using System.Xml;
using System.Reflection;
using Streamsol.Helpers;
using System.Xml.XPath;
namespace Streamsol.Crm.XrmDevelopmentToolkit
{
    internal static class XrmExtensions
    {
        internal static string GetErrorCode(this IXPathNavigable soapDetail)
        {
            return soapDetail.CreateNavigator().SelectSingleNode("//error//code").Value;
        }

        internal static Property CreateProperty(XrmFieldMappingAttribute xrmAttribute, object[] value)
        {
            return CreateProperty(xrmAttribute.CrmType, xrmAttribute.AttributeName, value);
        }

        internal static bool IsReferenceType(this AttributeType attributeType)
        {
            return (attributeType == AttributeType.Lookup ||
                attributeType == AttributeType.Owner ||
                attributeType == AttributeType.Customer ||
                attributeType == AttributeType.PrimaryKey);
        }

        internal static bool IsReferenceType(this XrmFieldMappingAttribute attribute)
        {
            return attribute.CrmType.IsReferenceType();
        }

        internal static XrmEntityAttribute GetXrmEntityAttribute(this IXrmEntity xrmEntity)
        {
            object[] attributes = xrmEntity.GetType().GetCustomAttributes(typeof(XrmEntityAttribute), false);
            if (attributes != null && attributes[0] != null)
                return ((XrmEntityAttribute)attributes[0]);
            throw new InvalidOperationException("The XrmEntity does not contain an XrmEntityAttribute");
        }

        internal static string GetCrmEntityName(this IXrmEntity xrmEntity)
        {
            return xrmEntity.GetXrmEntityAttribute().EntityName;
        }

        internal static void OnSaving(this IXrmEntity xrmEntity, DynamicEntity de)
        {
            var xrmAttribute = xrmEntity.GetXrmEntityAttribute();
            if (!string.IsNullOrEmpty(xrmAttribute.OnSaving))
                TryInvokeMethod(xrmEntity, xrmAttribute.OnSaving, de);
        }

        internal static void OnSaved(this IXrmEntity xrmEntity, Guid entityId)
        {
            var xrmAttribute = xrmEntity.GetXrmEntityAttribute();
            if (!string.IsNullOrEmpty(xrmAttribute.OnSaved))
                TryInvokeMethod(xrmEntity, xrmAttribute.OnSaved, entityId);
        }

        private static void TryInvokeMethod(object obj, string methodName, params object[] args)
        {
            try
            {
                MethodInfo mi = obj.GetType().GetMethod(methodName);
                mi.Invoke(obj, args);
            }
            catch
            {
                //swallow
            }
        }

        internal static Property CreateProperty(AttributeType attributeType, string attributeName, object[] value)
        {
            try
            {
                switch (attributeType)
                {
                    case AttributeType.Boolean:
                        return new CrmBooleanProperty(attributeName, new CrmBoolean((bool)value[0]));
                    case AttributeType.Customer:
                        return new CustomerProperty(attributeName, new Customer((string)value[1], (Guid)value[0]));
                    case AttributeType.DateTime:
                        return new CrmDateTimeProperty(attributeName, new CrmDateTime(((DateTime)value[0]).ToString("s")));
                    case AttributeType.Decimal:
                        return new CrmDecimalProperty(attributeName, new CrmDecimal((decimal)value[0]));
                    case AttributeType.Float:
                        return new CrmFloatProperty(attributeName, new CrmFloat((float)value[0]));
                    case AttributeType.Integer:
                        return new CrmNumberProperty(attributeName, new CrmNumber((int)value[0]));
                    case AttributeType.Lookup:
                        return new LookupProperty(attributeName, new Lookup() { Value = (Guid)value[0] });
                    case AttributeType.Money:
                        return new CrmMoneyProperty(attributeName, new CrmMoney((decimal)value[0]));
                    case AttributeType.Owner:
                        return new OwnerProperty(attributeName, new Owner((string)value[1], (Guid)value[0]));
                    case AttributeType.Picklist:
                        return new PicklistProperty(attributeName, new Picklist((int)value[0]));
                    case AttributeType.PrimaryKey:
                        return new KeyProperty(attributeName, new Key((Guid)value[0]));
                    case AttributeType.State:
                        return new StateProperty(attributeName, ((string)value[0]));
                    case AttributeType.Status:
                        return new StatusProperty(attributeName, new Status((int)value[0]));
                    case AttributeType.String:
                        return new StringProperty(attributeName, (string)value[0]);
                    case AttributeType.UniqueIdentifier:
                        return new KeyProperty(attributeName, new Key((Guid)value[0]));
                    default:
                        break;
                }
                throw new NotImplementedException(string.Format("Could not create a Property for {0} of type {1}",
                                                                 attributeName, attributeType.ToString()));
            }
            catch (InvalidCastException castEx)
            {
                string errMessage = string.Format(@"Could not cast the attribute {0} to the CRM type of '{1}'. If this field is to be read only please set the 'IsReadOnly' property of the  XrmAttribute to 'TRUE'.", attributeName, attributeType);
                throw new InvalidCastException(errMessage, castEx);
            }
        }

        internal static Func<object> ExtractValueFromCrmProperty(object crmValue, Type targetType)
        {
            Type crmType = crmValue.GetType();
            string propTypeNamespace = crmType.Namespace;
            string propTypeName = crmType.Name;
            string targetTypeName = targetType.FullName;

            //Check we are dealing with a CrmValue
            if (propTypeNamespace.Equals("Microsoft.Crm.Sdk"))
            {
                object encapsulatedValue = crmValue.ExtractPropertyValue("Value");

                if (encapsulatedValue.GetType().FullName.Equals(targetTypeName))
                {
                    return () => encapsulatedValue;
                }
                else if (propTypeName.Equals("CrmDateTime"))
                {
                    return () => DateTime.Parse(encapsulatedValue.ToString());
                }
                else if ((propTypeName.Equals("Lookup") || propTypeName.Equals("Customer") || propTypeName.Equals("Owner")) && targetTypeName.EndsWith("XrmReference"))
                {
                    return () =>
                    {
                        Guid id = (Guid)encapsulatedValue;
                        string type = crmValue.ExtractPropertyValue<string>("type");
                        string name = crmValue.ExtractPropertyValue<string>("name");
                        return new XrmReference(id, type, name);
                    };
                }
                else if (propTypeName.Equals("Status"))
                {
                    if (targetTypeName.EndsWith("XrmStatusInfo"))
                    {
                        return () => new XrmStatusInfo(crmValue.ExtractPropertyValue<string>("name"), (int)encapsulatedValue);
                    }
                    else if (targetTypeName.Equals("System.String"))
                    {
                        return () => crmValue.ExtractPropertyValue<string>("name");
                    }
                }
                else if (propTypeName.Equals("Picklist"))
                {
                    if (targetTypeName.EndsWith("XrmPicklistInfo"))
                    {
                        return () => new XrmPicklistInfo(crmValue.ExtractPropertyValue<string>("name"), (int)encapsulatedValue);
                    }
                    else if (targetTypeName.Equals("System.String"))
                    {
                        return () => crmValue.ExtractPropertyValue<string>("name");
                    }
                    else if (targetType.IsEnum)
                    {
                        return () => Enum.ToObject(targetType, encapsulatedValue);
                    }
                }
                else if (targetTypeName.Equals("System.Single") && encapsulatedValue is double)
                {
                    return () => (float)(double)encapsulatedValue;
                }
                else if (targetTypeName.Equals("System.Double") && encapsulatedValue is float)
                {
                    return () => (double)(float)encapsulatedValue;
                }
            }

            //If types match, then return (usually for string types)
            //if (propTypeName == targetTypeName)
            //{
            return () => crmValue;
            //}
        }
    }
}
